package org.jetlinks.pro.assets;

import org.apache.commons.codec.digest.DigestUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 资产相关工具类
 *
 * @author zhouhao
 * @since 1.11
 */
public class AssetsUtils {

    private static final String bindSourceContextKey = AssetsUtils.class.getName() + "_BIND_KEY";

    /**
     * 获取当前绑定源,通常用于判断绑定(解绑)方式.
     *
     * @param defaultIfEmpty 默认值
     * @return 绑定源
     * @see AssetBindManager#SOURCE_AUTO
     * @see AssetBindManager#SOURCE_MANUAL
     */
    public static Mono<String> currentBindSource(String defaultIfEmpty) {
        return Mono.subscriberContext()
                   .flatMap(ctx -> Mono.justOrEmpty(ctx.<String>getOrEmpty(AssetsUtils.bindSourceContextKey)))
                   .defaultIfEmpty(defaultIfEmpty);
    }

    /**
     * 给Mono设置绑定源
     * <pre>
     * mono
     *  .as(AssetsUtils.setMonoBindSource(AssetBindManager.SOURCE_AUTO))
     * </pre>
     *
     * @param source 绑定源
     * @param <T>    泛型
     * @return 绑定后的Mono
     * @see AssetBindManager#SOURCE_AUTO
     * @see AssetBindManager#SOURCE_MANUAL
     */
    public static <T> Function<Mono<T>, Mono<T>> setMonoBindSource(String source) {
        return mono -> mono.subscriberContext(Context.of(bindSourceContextKey, source));
    }


    /**
     * 给Flux设置绑定源
     *
     * <pre>
     * flux
     *   .as(AssetsUtils.setFluxBindSource(AssetBindManager.SOURCE_AUTO))
     * </pre>
     *
     * @param source 绑定源
     * @param <T>    泛型
     * @return 绑定后的Flux
     * @see AssetBindManager#SOURCE_AUTO
     * @see AssetBindManager#SOURCE_MANUAL
     */
    public static <T> Function<Flux<T>, Flux<T>> setFluxBindSource(String source) {
        return mono -> mono.subscriberContext(Context.of(bindSourceContextKey, source));
    }

    /**
     * 当绑定源为指定当值时,执行指定的Flux流.
     *
     * @param source         源
     * @param stream         Flux流
     * @param defaultIfEmpty 当未设置源时,是否默认执行
     * @param <T>            Flux流泛型
     * @return Flux
     */
    public static <T> Flux<T> doWhenSourceIs(String source, Flux<T> stream, boolean defaultIfEmpty) {
        return currentBindSource(defaultIfEmpty ? source : "")
            .filter(source::equals)
            .flatMapMany(str -> stream);
    }

    /**
     * 当绑定源为指定当值时,执行指定的Mono流.
     *
     * @param source         源
     * @param stream         Mono流
     * @param defaultIfEmpty 当未设置源时,是否默认执行
     * @param <T>            Mono流泛型
     * @return Flux
     */
    public static <T> Mono<T> doWhenSourceIs(String source, Mono<T> stream, boolean defaultIfEmpty) {
        return currentBindSource(defaultIfEmpty ? source : "")
            .filter(source::equals)
            .flatMap(str -> stream);
    }

    /**
     * 当绑定源为自动绑定时,执行指定的Flux流.
     *
     * @param stream         Flux流
     * @param defaultIfEmpty 当未设置源时,是否默认执行
     * @param <T>            Flux流泛型
     * @return Flux
     */
    public static <T> Flux<T> doWhenSourceIsAutoBind(Flux<T> stream, boolean defaultIfEmpty) {
        return doWhenSourceIs(AssetBindManager.SOURCE_AUTO, stream, defaultIfEmpty);
    }

    /**
     * 当绑定源为自动绑定时,执行指定的Mono流.
     *
     * @param stream         Mono流
     * @param defaultIfEmpty 当未设置源时,是否默认执行
     * @param <T>            Mono流泛型
     * @return Flux
     * @see AssetBindManager#SOURCE_AUTO
     */
    public static <T> Mono<T> doWhenSourceIsAutoBind(Mono<T> stream, boolean defaultIfEmpty) {
        return doWhenSourceIs(AssetBindManager.SOURCE_AUTO, stream, defaultIfEmpty);
    }

    /**
     * 当绑定源为手动绑定时,执行指定的Flux流.
     *
     * @param stream         Flux流
     * @param defaultIfEmpty 当未设置源时,是否默认执行
     * @param <T>            Flux流泛型
     * @return Flux
     * @see AssetBindManager#SOURCE_MANUAL
     */
    public static <T> Flux<T> doWhenSourceIsManualBind(Flux<T> stream, boolean defaultIfEmpty) {
        return doWhenSourceIs(AssetBindManager.SOURCE_MANUAL, stream, defaultIfEmpty);
    }

    /**
     * 当绑定源为手动时,执行指定的Mono流.
     *
     * @param stream         Mono流
     * @param defaultIfEmpty 当未设置源时,是否默认执行
     * @param <T>            Mono流泛型
     * @return Flux
     * @see AssetBindManager#SOURCE_MANUAL
     */
    public static <T> Mono<T> doWhenSourceIsManualBind(Mono<T> stream, boolean defaultIfEmpty) {
        return doWhenSourceIs(AssetBindManager.SOURCE_MANUAL, stream, defaultIfEmpty);
    }

    /**
     * 将维度绑定信息转为简单的Map,用于进行持久化等操作.
     * <p>
     * 格式:
     * {"type":"绑定维度类型","id":"绑定维度ID"}
     *
     * @param binding 绑定信息
     * @return 简单Map
     */
    public static Map<String, Object> bindingToMap(DimensionBinding binding) {
        Map<String, Object> map = new HashMap<>();
        map.put("type", binding.getTargetType().getId());
        map.put("id", binding.getTargetId());
        return map;
    }

    /**
     * 将资产绑定信息转为简单的Map,用于进行持久化等操作.
     * <p>
     * 格式:
     * {"type":"绑定维度类型","id":"绑定维度ID"}
     *
     * @param binding 绑定信息
     * @return 简单Map
     */
    public static Map<String, Object> bindingToMap(AssetBinding binding) {
        Map<String, Object> map = new HashMap<>();
        map.put("type", binding.getTargetType().getId());
        map.put("id", binding.getTargetId());
        return map;
    }

    /**
     * 将资产绑定信息转为简单的Map,用于进行持久化等操作.
     * <p>
     * 格式:
     * [{"type":"绑定维度类型","id":"绑定维度ID"}]
     *
     * @param bindings 绑定信息
     * @return 简单Map
     */
    public static List<Map<String, Object>> bindingsToMaps(List<AssetBinding> bindings) {
        return bindings
            .stream()
            .map(AssetsUtils::bindingToMap)
            .collect(Collectors.toList());
    }

    /**
     * 将维度绑定信息转为简单的Map,用于进行持久化等操作.
     * <p>
     * 格式:
     * [{"type":"绑定维度类型","id":"绑定维度ID"}]
     *
     * @param bindings 绑定信息
     * @return 简单Map
     */
    public static List<Map<String, Object>> bindingTargetsToMaps(List<DimensionBinding> bindings) {
        return bindings
            .stream()
            .map(AssetsUtils::bindingToMap)
            .collect(Collectors.toList());
    }

    /**
     * 根据绑定维度类型和绑定ID创建一个绑定key
     *
     * @param targetType 绑定维度类型
     * @param targetId   维度ID
     * @return 绑定key
     */
    public static String createBindingTargetKey(String targetType, String targetId) {
        return DigestUtils.md5Hex(String.join("|", targetType, targetId));
    }

    /**
     * 根据一个绑定Map来创建一个绑定key
     *
     * @param binding 绑定信息
     * @return 绑定Key
     */
    public static String createBindingTargetKey(Map<String, Object> binding) {
        return createBindingTargetKey(String.valueOf(binding.get("type")), String.valueOf(binding.get("id")));
    }

    /**
     * 将map格式等维度绑定信息转为绑定key
     *
     * @param bindings 绑定信息
     * @return 绑定Key
     */
    public static List<String> bindingsMapToKeys(List<Map<String, Object>> bindings) {
        return bindings
            .stream()
            .map(AssetsUtils::createBindingTargetKey)
            .collect(Collectors.toList());
    }


    /**
     * 将map格式等维度绑定信息转为维度绑定信息对象
     *
     * @param bindings map绑定信息
     * @return 维度绑定信息
     */
    public static List<DimensionInfo> bindingsMapToInfo(List<Map<String, Object>> bindings) {
        return bindings
            .stream()
            .map(map -> DimensionInfo.of(map).orElse(null))
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    }

}
